/*
 *------------------------------------------------------------------------------
 * sched.c - routines for the scheduler module
 *
 * 9-20-2008, Forhad Ahmed
 *------------------------------------------------------------------------------
 */

#include <stdlib.h>

#include "sched.h"
#include "proc.h"


/*
 * sched_prepare(proc *p)
 *
 *   Function called for every pcb that is schedule()'d. The pcb scheduling
 *   related fields will be reset.
 */
static inline void sched_prepare(proc *pcb)
{
    if (pcb != NULL) {
        pcb->state = SCHED_READY;
        pcb->timer = SCHED_QUANTUM;
    }
}


/*
 * schedule_internal(proc *prev, proc *new)
 *
 *   When a process is scheduled, it *might* displace the currently running
 *   process (head of runq) and become the new head of the runq (which means it
 *   will be executing in the next timeslice).  If this happens, we need to
 *   reset the currently running process' timer quantum as well as set its state
 *   to SCHED_READY. Calling this function at every schedule() ensures this
 *   essential bookkeeping takes place.
 */
static void schedule_internal(proc *prev, proc *new)
{
    proc *head;

    head = (proc*) runq->head;
    if (new == head) {
        /*
         * As a result of scheduling 'new', it has now become the head of runq.
         * This means that the previously running process has been rescheduled.
         * In this case, we must reset the timer quantum of the previously
         * running process and set it's state to 'SCHED_READY'
         */
         sched_prepare(prev);
    }
}


/*
 * schedule(proc *pcb)
 *
 * Core scheduler function.  Modify this to alter the scheduling algorithm
 * (see sched.h for details)
 */
void schedule(proc *pcb)
{
    proc *new = pcb;
    proc *current = (proc*) runq->head;

    sched_prepare(new);

    /*
     * The heart of the scheduler:
     */
    list_prepend(runq, new);

    schedule_internal(current, new);
}


/*
 * sched_timer_interrupt(proc *curr)
 *
 *   Simulates a (synchronous) timer interrupt.  After each timeslice, the
 *   quantum of the currently running process is decremented, and if it reaches
 *   0, we reschedule the process.  Here, we also walk the waitq of processes
 *   and decrement their sleep timers if any of them are sleeping.  If any pcb's
 *   sleep timer reaches 0, we delink that pcb from the waitq and reschedule it
 *   back into the runq.
 */
static void sched_timer_interrupt(proc *curr)
{
    node *n;
    proc *p;

    /*
     * Increment the global time
     */
    stime++;

    /*
     * Re-schedule the currently running process if its time quantum expired.
     */
    p = curr;
    if (p != NULL && p->state == SCHED_RUNNING) {
        if (--p->timer == 0) {
            list_delink(runq, p);
            schedule(p);
            //printf(" %5d : kernel| %d quantum expired. rescheduled\n", stime, p->pid);
        }
    }

    /*
     * For all sleeping processes, decrement their sleep timers and if any hit
     * zero, reschedule them back into the runq.
     */
    LIST_FOR_ALL_NODES(waitq, n) {
        p = (proc *) n;
        if (p->state == SCHED_SLEEP_WAIT) {
            if (--p->timer == 0) {
                list_delink(waitq, p);
                schedule(p);
                //printf(" %5d : kernel| %d unblocked\n", stime, p->pid);
            }
        }
    }
}


/*
 * sched_init()
 *
 *   Initialization of the scheduler module.
 */
static void sched_init()
{
    stime = 0;
    runq = list_new();
    waitq = list_new();
    resourceq = list_new();
    sleep_wait = 0;
    resource_wait = 0;
}


/*
 *
 */
static void sched_free()
{
    list_free(runq, free);
    list_free(waitq, free);
    list_free(resourceq, free);
}


/*
 *
 *
 */
void sched_step()
{
    proc *current;

    current = (proc*) runq->head;
    if (current) current->state = SCHED_RUNNING;
    proc_execute(current);
    sched_timer_interrupt(current);
}


/*
 *
 */
static void sched_run()
{
    while (1) {
        sched_step();
    }
}


/*
 *
 */
char *sched_state_string(int state)
{
    switch (state) {
    case SCHED_READY:
        return "READY";
    case SCHED_RUNNING:
        return "RUNNING";
    case SCHED_SLEEP_WAIT:
        return "SLEEP";
    case SCHED_RESOURCE_WAIT:
        return "RESOURCE";
    default:
        return "(unknown)";
    }
}

/*
 *
 *
 */
void dumpcore()
{

}
